package com.gitee.wsl.flow.coroutines

import com.gitee.wsl.ext.base.currentTimeMillis
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant


const val DEFAULT_THROTTLE_DURATION = 1000L
const val LONG_THROTTLE_DURATION = 2000L

fun <T> Flow<T>.throttle(waitMillis: Int) = flow {
    coroutineScope {
        val context = coroutineContext
        var nextTime = 0L
        var delayPost: Deferred<Unit>? = null
        collect {
            val current = currentTimeMillis
            if (nextTime < current) {
                nextTime = current + waitMillis
                emit(it)
                delayPost?.cancel()
            } else {
                val delayNext = nextTime
                delayPost?.cancel()
                delayPost = this.async(Dispatchers.Default) {
                    delay(nextTime - current)
                    if (delayNext == nextTime) {
                        nextTime = currentTimeMillis + waitMillis
                        withContext(context) {
                            emit(it)
                        }
                    }
                }
            }
        }
    }
}

/**
 * Throttles a flow at a specific interval. This will only return one emission during the specified period.
 */
//fun <T> Flow<T>.throttle(periodMillis: Long): Flow<T> = flow {
//    var windowStartTime = Instant.DISTANT_PAST
//
//    collect { value ->
//        val currentTime = Clock.System.now()
//        val delta = currentTime - windowStartTime
//
//        if (delta.inWholeMilliseconds < periodMillis) return@collect
//
//        windowStartTime += delta
//        emit(value)
//    }
//}

fun <T> Flow<T>.throttleFirst(windowDuration: Long = DEFAULT_THROTTLE_DURATION): Flow<T> = flow {
    var windowStartTime = currentTimeMillis
    var emitted = false
    collect { value ->
        val currentTime = currentTimeMillis
        val delta = currentTime - windowStartTime
        if (delta >= windowDuration) {
            windowStartTime += delta / windowDuration * windowDuration
            emitted = false
        }
        if (!emitted) {
            emit(value)
            emitted = true
        }
    }
}